/*
 * SignatureSliceFileHandler.h
 *
 *  Created on: Nov 25, 2012
 *      Author: m
 */

#ifndef FILEHANDLERIMPLEMENTATION_H_
#define FILEHANDLERIMPLEMENTATION_H_

#include "FileHandlerInterface.h"
#include "../../../physical_layer/common_components/BlockFileWithByteAccess.h"
#include "../../../physical_layer/serializers/BitSetSerializer.h"

// SLICE_SIZE es el tamaño en bits de la Porción de Firma
#define SLICE_SIZE 128

// SLICE_SIZE_IN_BYTES es el tamaño en bytes de la Porción de Firma
#define SLICE_SIZE_IN_BYTES SLICE_SIZE/8

using physical::byte;
using physical::BlockFileWithByteAccess;
using physical::Serializer;
using physical::BitSetSerializer;

namespace SignatureSlicingIndex
{

/**
 * Clase que implementa la interfáz FileHandlerInterface
 *
 * Por favor cambiarle el nombre a esa interfaz a algo menos generico, como
 * SignatureSliceFileHandlerInterface sino dsp nos puede tirar problemas con
 * el árbol y el makefile (aunque esté en otro namespace)
 */

class SignatureSliceFileHandler: public FileHandlerInterface<SLICE_SIZE>
{
public:

    /**
     * Constructor
     *
     * Crea un SignatureSliceFileHandler y le asocia el archivo
     *  filename. Si truncate es TRUE, trunca el archivo, sino
     *  solamente lo abre (y lo crea y no existe).
     *
     * @param fileName Nombre del archivo de Porciones de Firmas
     * @param truncate Booleano que indica si el archivo se debe
     *         truncar. Valor por defecto: false
     */
    SignatureSliceFileHandler( const char* fileName, bool truncate = false ) :
            m_file( new BlockFileWithByteAccess( SLICE_SIZE_IN_BYTES ) ),
            m_serializer( new BitSetSerializer<SLICE_SIZE> )
    {
        bool fileExists;
        /*
         * Acá voy a hacer algo de magia
         *
         * Según el estandar de C++, la única manera de abrir un
         *  archivo que no existe en modo "in" es con trunc
         *  (independientemente de que le pongas también out)
         *
         *  Asi que, si queremos que no tire un error cuando le
         *  pedimos abrir un archivo que no existe, vamos a tener
         *  que verificar primero si el archivo existe; si existe
         *  lo abrimos tranquilos y si no existe lo creamos vacío
         *  y después lo abrimos
         */

        std::ifstream file( fileName );
        fileExists= file.is_open();
        file.close();

        if ( fileExists && truncate == false )
        {
            // Si existe lo cierro y lo re-abro como un blockfile

            m_file->open( fileName, s_defaultOpenmode );
        }
        else
        {
            // Si no existe lo creo como un blockfile

            m_file->open( fileName, s_defaultOpenmode | std::ios_base::trunc );
        }

    }

    /**
     * Destructor
     */
    virtual ~SignatureSliceFileHandler()
    {
        m_file->close();
    }

    /**
     * Devuelve una Porción de Firma
     *
     * @param KeyWord Id de la Porción de Firma
     * @return Un puntero a la Porción de Firma
     */
    Slice_ptr getSlice( const KeyWordID& KeyWord )
    {
        // Primero deserializo el bitset

        byte buffer[ SLICE_SIZE_IN_BYTES];
        std::bitset<SLICE_SIZE> signature;

        // Para ello leo el bloque especificado y deserializo el bitset
        m_file->readBlock( KeyWord, buffer );

        m_serializer->deserialize( buffer, signature );

        // Finalmente creo el Slice
        Slice_ptr result( new Slice( KeyWord, signature ) );

        return result;
    }

    /**
     * Graba una Porción de Firma
     *
     * @param slice Puntero a la Porción de Firma
     */
    void saveSlice( const Slice_ptr& slice )
    {
        byte buffer[ SLICE_SIZE_IN_BYTES];
        auto blockNbr = slice->getID();

        // serializo el bitset
        m_serializer->serialize( slice->getSignature(), buffer );

        // lo grabo en el archivo
        m_file->writeBlock( blockNbr, buffer );
    }

    /**
     * Actualiza un bit de una Porción de Firma
     *
     * Este método está optimizado para reducir al mínimo
     *  los accesos a disco cuando solo se desea modificar
     *  un único bit de toda una firma
     *
     * @param sliceNumber Id del Slice
     * @param bitNumber   bit a modificar (posición relativa dentro del Slice)
     * @param value Valor en que queres setea el bit ( true para 1, false para 0 )
     */
    void updateSliceBit( const size_t sliceNumber, size_t bitNumber,
            bool value )
    {
        // Parseo el bit number a un byteOffset y bitOffset
        size_t byteOffset = bitNumber / 8;
        size_t bitOffset = bitNumber % 8;

        // Leo el byte
        byte buffer = m_file->readByte( sliceNumber, byteOffset );

        // cambio el bit que corresponda
        buffer = this->setBit( bitOffset, buffer, value );

        // lo grabo en el archivo
        m_file->writeByte( sliceNumber, byteOffset, buffer );
    }

    /**
     * Obtiene el ID del primer slice
     *
     * @return ID del primer slice
     */
    size_t begin()
    {
        return 0;
    }

    /**
     * Obtiene el ID del último slice +1
     *
     * @return ID del último slice + 1
     */
    size_t end()
    {
        return m_file->getBlocksCount();
    }

protected:

    static const std::ios_base::openmode s_defaultOpenmode;

    std::unique_ptr<physical::BlockFileWithByteAccess> m_file;
    std::unique_ptr<physical::Serializer<std::bitset<SLICE_SIZE> > > m_serializer;

    /**
     * Setea en 1 o 0 un determinado bit de un byte
     *
     * @param theBit Bit a setear/resetear
     * @param theByte Byte sobre el que se opera
     * @param value Valor en el que poner el bit
     * @return El byte con el bit seteado en el valor
     *  correspondiente
     */
    byte setBit( size_t theBit, byte theByte, bool value )
    {
        byte result;

        if ( value == true )
        {
            result = theByte | 0x01 << theBit;
        }
        else
        {
            result = theByte & ~( 0x01 << theBit );
        }

        return result;
    }
};

const std::ios_base::openmode SignatureSliceFileHandler::s_defaultOpenmode =
        std::ios_base::in | std::ios_base::out | std::ios_base::binary;

} /* namespace SignatureSlicingIndex */
#endif /* FILEHANDLERINTERFACE_H_ */
